/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.google.code.twiddling.core.registry;

import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Logger;

import com.google.code.twiddling.core.command.ICommand;
import com.google.code.twiddling.core.command.Command;
import com.google.code.twiddling.core.exception.TwiddlingException;
import com.google.code.twiddling.core.log.LogUtils;
import com.google.code.twiddling.core.log.Message;
import com.google.code.twiddling.core.util.ASMUtils;

/**
 * 
 * Read Commands class from the classpath's jars by finding CommandName annotation.
 * 
 * @author <a href="mailto:jeff.yuchang@gmail.com">Jeff Yu</a>
 */
public class AnnotationCommandRegistry implements CommandRegistry {
	
	private static final Logger logger = LogUtils.getL7dLogger(AnnotationCommandRegistry.class);
	
	private static final ResourceBundle bundle = logger.getResourceBundle();
	
	private Map<String, Class<? extends ICommand>> data = new HashMap<String, Class<? extends ICommand>>();
	
	private Map<String, String> aliasRegistry = new HashMap<String, String>();
	
	public AnnotationCommandRegistry() throws TwiddlingException {
		initialize();
	}
	
	
	@SuppressWarnings("unchecked")
	private void initialize() throws TwiddlingException{
		try {		
			Class[] classes = ASMUtils.parseAnnoation((URLClassLoader)ASMUtils.class.getClassLoader(), Command.class);			
			for (Class clz : classes) {
				if (ICommand.class.isAssignableFrom(clz)) {
				   registerCommandFromAnnotation(clz);
				} else {
					throw new TwiddlingException(
						  new Message("NOT_IMPLEMENT_ICOMMAND_INTERFACE", bundle, new Object[]{clz}).toString());
				}				
			}
		} catch (Exception e) {
			throw new TwiddlingException(e);
		}		
	}
	
	/**
	 * 
	 * @param clazz
	 * @throws ClassNotFoundException
	 */
	private void registerCommandFromAnnotation(Class<? extends ICommand> clz) throws DuplicateCommandException {		
		Command command = clz.getAnnotation(Command.class);
		
		if (command == null || command.name() == null) {
			throw new NullPointerException(
					new Message("DONT_HAS_COMMAND_ANNOTATION", bundle, new Object[]{clz.toString()}).toString());
		}
		if (data.get(command.name()) != null) {
			throw new DuplicateCommandException(
					new Message("COMMAND_REGISTERED_DUPLICATED", bundle, new Object[]{clz.toString()}).toString());
		}
		
		data.put(command.name(), clz);
		
		String[] aliases = command.aliases();
		for (String alias : aliases) {
			if (alias != null && !"".equals(alias.trim())) {
				if (aliasRegistry.get(alias) != null) {
					throw new DuplicateCommandException(
							new Message("ALIAS_REGISTERED_DUPLICATED", bundle, new Object[]{alias}).toString());
				}
				aliasRegistry.put(alias, command.name());
			}
		}
		
	}

	public boolean containsCommandName(String name) {		
		return (data.containsKey(name) || aliasRegistry.containsKey(name));
	}

	public Class<? extends ICommand> getCommandClass(String name) throws CommandNotFoundException{
		Class<? extends ICommand> clz = data.get(name);
		if (clz == null) {
			String key = aliasRegistry.get(name);
			clz = data.get(key);
			if (clz == null) {
				throw new CommandNotFoundException(
						new Message("COMMAND_NOT_FOUND", bundle, new Object[]{name}).toString());
			}
		}
		return clz;
	}

	public Set<String> getCommandNames() {
		return data.keySet();
	}

	public void registerCommand(Class<? extends ICommand> command) throws DuplicateCommandException {
		registerCommandFromAnnotation(command);
	}

	public void removeCommand(Class<? extends ICommand> command) {
		Command commandName = command.getAnnotation(Command.class);
		if (commandName == null || commandName.name() == null) {
			throw new NullPointerException(
					new Message("DONT_HAS_COMMAND_ANNOTATION", bundle, new Object[]{command.toString()}).toString());
		}
		data.remove(commandName.name());
	}


	public ICommand newCommandInstance(String name) throws CommandNotFoundException{
		Class<? extends ICommand> clz = this.getCommandClass(name);
		try {
			ICommand obj = clz.newInstance();
			return obj;
		} catch (InstantiationException e) {
			throw new RuntimeException ("error in instance the Command class of " + clz, e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException ("error in instance the Command class of " + clz, e);
		}
	}
	
	
}
